package com.urbane.generator.service.impl;

import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.example.codegenweb.entity.DataSources;
import com.example.codegenweb.entity.Template;
import com.example.codegenweb.service.DataSourceService.TableInfo;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 代码生成核心服务：根据配置动态生成代码并打包为 ZIP
 *
 * @author your-name
 * @date 2024-06-25
 */
@Service
public class CodeGenService {

    @Autowired
    private DataSourceService dataSourceService;

    @Autowired
    private TemplateService templateService;

    // 临时生成目录（生成后删除）
    private static final String TEMP_GEN_DIR = "temp-gen";

    /**
     * 执行代码生成并返回 ZIP 文件路径
     *
     * @param dataSourceId 数据源ID
     * @param tableNames   要生成的表名数组（如 ["t_user", "t_product"]）
     * @param templateMap  模板映射：{ "vue-list": "vue-list.vue.ftl", ... }
     * @param author       作者
     * @return 生成的 ZIP 文件路径
     */
    public String generateCode(Long dataSourceId, List<String> tableNames, Map<String, String> templateMap, String author) {
        // 1. 加载数据源
        DataSources source = dataSourceService.getDataSourcesMapper().selectById(dataSourceId);
        if (source == null) {
            throw new IllegalArgumentException("数据源不存在");
        }

        // 2. 准备输出目录
        Path tempPath = Paths.get(TEMP_GEN_DIR);
        try {
            Files.createDirectories(tempPath);
        } catch (IOException e) {
            throw new RuntimeException("创建临时目录失败", e);
        }

        // 3. 构建后端输出路径
        String javaOutputPath = tempPath.resolve("src/main/java").toString();
        String frontendOutputPath = tempPath.resolve("frontend/src").toString();

        // 4. 配置全局设置
        GlobalConfig gc = new GlobalConfig();
        gc.setAuthor(author)
          .setOutputDir(javaOutputPath)
          .setFileOverride(false)
          .setActiveRecord(false)
          .setEnableCache(false)
          .setBaseResultMap(true)
          .setBaseColumnList(true)
          .setOpen(false);

        // 5. 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl(source.getUrl())
           .setUsername(source.getUsername())
           .setPassword(source.getPassword())
           .setDriverName(source.getDriverClass())
           .setDbType(com.baomidou.mybatisplus.annotation.DbType.valueOf(source.getDbType()));

        // 6. 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.example")
          .setEntity("entity")
          .setMapper("mapper")
          .setService("service")
          .setServiceImpl("service.impl")
          .setController("controller")
          .setXml("mapper");

        // 7. 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel)
                .setColumnNaming(NamingStrategy.underline_to_camel)
                .setEntityLombokModel(true)
                .setRestControllerStyle(true)
                .setInclude(tableNames.toArray(new String[0]))
                .setTablePrefix("t_")
                .setEntityTableFieldAnnotationEnable(true)
                .setControllerMappingHyphenStyle(true);

        // 8. 模板配置：动态加载模板内容
        TemplateConfig templateConfig = new TemplateConfig();

        // 后端模板
        templateConfig.setEntity(null); // 不使用默认模板
        templateConfig.setMapper(null);
        templateConfig.setService(null);
        templateConfig.setServiceImpl(null);
        templateConfig.setController(null);
        templateConfig.setXml(null);

        // 动态加载前端模板
        for (Map.Entry<String, String> entry : templateMap.entrySet()) {
            String templateName = entry.getKey(); // 如：vue-list
            String templateFileName = entry.getValue(); // 如：vue-list.vue.ftl

            // 从数据库中获取模板内容
            String templateContent = templateService.getTemplateContentByNameAndType(templateFileName, templateName.toUpperCase());
            if (templateContent == null) {
                throw new IllegalArgumentException("模板 " + templateFileName + " 不存在");
            }

            // 保存模板内容到临时文件供 Freemarker 使用
            Path templatePath = Paths.get(TEMP_GEN_DIR, "templates", templateFileName);
            try {
                Files.createDirectories(templatePath.getParent());
                Files.write(templatePath, templateContent.getBytes("UTF-8"));
            } catch (IOException e) {
                throw new RuntimeException("写入模板文件失败：" + templateFileName, e);
            }

            // 注册模板到模板配置中
            // 注意：MyBatis-Plus Generator 不支持动态模板路径，我们只能通过文件路径加载
            // 因此我们把模板写入文件系统，再让 Generator 加载
            switch (templateName) {
                case "vue-list":
                    templateConfig.setOther("/templates/" + templateFileName);
                    break;
                case "vue-form":
                    templateConfig.setOther("/templates/" + templateFileName);
                    break;
                case "ts-api":
                    templateConfig.setOther("/templates/" + templateFileName);
                    break;
                case "ts-types":
                    templateConfig.setOther("/templates/" + templateFileName);
                    break;
                default:
                    // Java 模板
                    templateConfig.setEntity("/templates/" + templateFileName);
            }
        }

        // 9. 创建生成器
        AutoGenerator mpg = new AutoGenerator();
        mpg.setGlobalConfig(gc)
           .setDataSource(dsc)
           .setPackageInfo(pc)
           .setStrategy(strategy)
           .setTemplate(templateConfig)
           .setTemplateEngine(new FreemarkerTemplateEngine());

        // 10. 执行生成（生成到临时目录）
        try {
            mpg.execute();
        } catch (Exception e) {
            throw new RuntimeException("代码生成失败：" + e.getMessage(), e);
        }

        // 11. 打包为 ZIP 文件
        String zipFileName = "codegen_" + System.currentTimeMillis() + ".zip";
        String zipPath = TEMP_GEN_DIR + "/" + zipFileName;

        try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(new FileOutputStream(zipPath))) {
            // 添加后端 Java 代码
            addFolderToZip(zos, Paths.get(javaOutputPath), "");
            // 添加前端代码
            addFolderToZip(zos, Paths.get(frontendOutputPath), "frontend/src/");
            // 添加模板文件（用于后续编辑）
            addFolderToZip(zos, Paths.get(TEMP_GEN_DIR + "/templates"), "templates/");

        } catch (IOException e) {
            throw new RuntimeException("打包 ZIP 失败", e);
        }

        // 12. 清理临时文件
        deleteDirectory(tempPath.toFile());

        return zipFileName;
    }

    /**
     * 将文件夹添加到 ZIP
     */
    private void addFolderToZip(ZipArchiveOutputStream zos, Path path, String basePath) throws IOException {
        if (!Files.exists(path)) return;
        Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                String entryName = basePath + file.toString().replace("\\", "/").replace(path.toString() + "/", "");
                zos.putArchiveEntry(new ZipArchiveEntry(entryName));
                IOUtils.copy(Files.newInputStream(file), zos);
                zos.closeArchiveEntry();
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * 删除目录
     */
    private void deleteDirectory(File dir) {
        if (dir == null || !dir.exists()) return;
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    deleteDirectory(file);
                } else {
                    file.delete();
                }
            }
        }
        dir.delete();
    }
}